package common.server.utils;

import java.io.IOException;
import java.io.InputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.Proxy;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import common.server.http.KeyValue;
import common.server.http.NameValuePair;


public class HttpTools {

	private static Log logger = LogFactory.getLog(HttpTools.class);
	public static final int CONNECT_TIMEOUT = 3000;
	public static final int READ_TIMEOUT = 10000;
	public static final int GET = 1;
	public static final int POST = 2;
	public static SSLSocketFactory sslSocketFactory;

	/**
	 * 初始化ssl
	 */
	static {
		TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {

			@Override
			public X509Certificate[] getAcceptedIssuers() {
				return null;
			}

			@Override
			public void checkServerTrusted(X509Certificate[] certs, String authType) throws CertificateException {
			}

			@Override
			public void checkClientTrusted(X509Certificate[] certs, String authType) throws CertificateException {
			}
		} };

		try {
			SSLContext sc = SSLContext.getInstance("TLS");
			sc.init(null, trustAllCerts, new SecureRandom());
			sslSocketFactory = sc.getSocketFactory();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 新建连接
	 * 
	 * @param url
	 * @param proxy
	 * @param method
	 *            HttpTools.GET / HttpTools.POST
	 * @param connectTimeout
	 *            连接超时
	 * @param readTimeout
	 *            读取超时
	 * @param autoRedirect
	 *            是否自动跳转30X请求
	 * @param headers
	 * @param cookieStore
	 * @return
	 * @throws IOException
	 */
	static HttpURLConnection openConnection(String url, Proxy proxy, int method, int connectTimeout, int readTimeout, boolean autoRedirect, Map<String, String> headers, CookieStore cookieStore) throws IOException {

		URL u = new URL(url);
		HttpURLConnection connection = proxy == null ? (HttpURLConnection) u.openConnection() : (HttpURLConnection) u.openConnection(proxy);

		if (connection instanceof HttpsURLConnection) {
			((HttpsURLConnection) connection).setSSLSocketFactory(sslSocketFactory);
		}

		connection.setUseCaches(false);
		connection.setInstanceFollowRedirects(autoRedirect);

		if (connectTimeout <= 0) {
			connectTimeout = CONNECT_TIMEOUT;
		}
		if (readTimeout <= 0) {
			readTimeout = READ_TIMEOUT;
		}
		connection.setConnectTimeout(connectTimeout);
		connection.setReadTimeout(readTimeout);

		switch (method) {
		case GET:
			connection.setRequestMethod("GET");
			break;

		case POST:
			connection.setDoOutput(true);
			connection.setUseCaches(false);
			connection.setRequestMethod("POST");
			break;
		}

		if (headers != null) {
			for (String key : headers.keySet()) {
				connection.addRequestProperty(key, headers.get(key));
			}
		}

		if (logger.isDebugEnabled()) {
			logger.debug("建立连接[" + connection.getRequestMethod() + "] : " + url);
		}

		return connection;
	}

	/**
	 * 获取响应
	 * 
	 * @param client
	 * @param connection
	 * @param cookieStore
	 * @return
	 * @throws Exception
	 */
	static HttpResponse getResponse(HttpClient client, HttpURLConnection connection, CookieStore cookieStore) throws Exception {

		PipedInputStream in = null;
		PipedOutputStream out = null;
		ContentSender sender = null;

		try {

			int responseCode = connection.getResponseCode();
			String redirectUrl = connection.getURL().toString();

			Map<String, String> headers = new HashMap<String, String>();
			for (String key : connection.getHeaderFields().keySet()) {
				headers.put(key, connection.getHeaderField(key));
			}
			headers = Collections.unmodifiableMap(headers);

			if (responseCode == 200 || (!client.isAutoRedirect() && (responseCode == 301 || responseCode == 302))) {

				sender = new ContentSender();
				sender.setConnection(connection);

				in = new PipedInputStream(102400);
				out = sender.getPipedOutputStream();
				in.connect(out);

				Thread senderThread = new Thread(sender);
				senderThread.start();

				return new HttpResponse(connection, responseCode, redirectUrl, headers, in);
			} else {

				if (client.isAutoRedirect() && (responseCode == 301 || responseCode == 302)) {

					String redirect = headers.get("Location");

					Map<String, List<String>> requestHeaders = connection.getRequestProperties();
					Map<String, String> sendHeaders = new HashMap<String, String>();
					if (requestHeaders != null) {
						for (String name : requestHeaders.keySet()) {
							if (name != null) {
								List<String> list = requestHeaders.get(name);
								if (list != null && list.size() > 0) {
									sendHeaders.put(name, list.get(0));
								}
							}
						}
					}
					
					try {
						if (logger.isDebugEnabled()) {
							logger.debug("关闭连接[" + connection.getRequestMethod() + "] : " + connection.getURL() + " - HttpTools " + responseCode);
						}
						connection.disconnect();
					} catch (Exception e) {
					}
					
					return client.get(redirect, sendHeaders);

				} else {

					return new HttpResponse(connection, responseCode, redirectUrl, headers, null);
				}

			}

		} catch (Exception e) {
			if (in != null) {
				try {
					in.close();
				} catch (Exception e2) {
				}
			}
			if (out != null) {
				try {
					out.close();
				} catch (Exception e2) {
				}
			}
			try {
				if (logger.isDebugEnabled()) {
					logger.debug("关闭连接[" + connection.getRequestMethod() + "] : " + connection.getURL() + " - HttpTools.getResponse Error");
				}
				connection.disconnect();
			} catch (Exception e2) {
			}
			throw e;
		}
	}

	/**
	 * 拼接请求地址
	 * 
	 * @param url
	 * @param parameters
	 * @param charset
	 * @return
	 */
	public static String getRequestUrl(String url, List<NameValuePair<String>> parameters, String charset) {

		StringBuilder sb = new StringBuilder(url);
		int index = url.indexOf("?");
		if (index >= 0) {
			if (index < url.length() - 1) {
				index = url.lastIndexOf("&");
				if (index < url.length() - 1) {
					sb.append("&");
				}
			}
		} else {
			sb.append("?");
		}

		if (parameters != null) {
			if (charset == null || "".equals(charset.trim())) {
				charset = "utf-8";
			}
			for (KeyValue<String, String> pair : parameters) {
				try {
					sb.append(URLEncoder.encode(pair.getKey(), charset) + "=" + URLEncoder.encode(pair.getValue(), charset));
				} catch (UnsupportedEncodingException e) {
					e.printStackTrace();
				}
				sb.append("&");
			}
		}

		return sb.substring(0, sb.length() - 1);
	}

	/**
	 * 读取响应文本
	 * 
	 * @param response
	 * @param forceEncoding
	 *            只使有charset参数,忽略响应头信息的编码参数
	 * @param charset
	 *            读取编码
	 * @return
	 */
	public static String convertToString(HttpResponse response, boolean forceEncoding, String charset) {

		InputStream in = null;
		try {
			in = response.getInputStream();
			String useCharset = null;
			charset = StringUtils.isNotEmpty(charset) ? charset : "utf-8";

			if (forceEncoding) {
				useCharset = charset;
			} else {
				String contentType = response.getHeader("content-type");
				int index = contentType == null ? -1 : contentType.indexOf("charset=");
				String responseCharset = index >= 0 ? contentType.substring(index + 8) : null;

				try {
					Charset.forName(charset);
				} catch (Exception e) {
					responseCharset = null;
				}
				useCharset = responseCharset != null ? responseCharset : charset;
			}

			byte[] bytes = IOUtils.toByteArray(in);
			return new String(bytes, useCharset);

		} catch (Exception e) {
			throw new RuntimeException("HTTP响应转换字符串异常", e);

		} finally {
			if (in != null) {
				try {
					in.close();
				} catch (Exception e) {
				}
			}
		}
	}

}
